﻿using GodSharp.Communications.Abstractions;
using GodSharp.Sockets;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

namespace GodSharp.Communications.Socket
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="destination"></param>
    /// <param name="buffer"></param>
    /// <param name="sender"></param>
    internal delegate void UdpCommunicationDataEventHandler(string destination, byte[] buffer, IUdpConnection sender);

#pragma warning disable CS1584 // XML comment has syntactically incorrect cref attribute
#pragma warning disable CS1658 // Warning is overriding an error
    /// <summary>
    /// TcpCommunicationProvider
    /// </summary>
    /// <seealso cref="Abstractions.CommunicationProviderBase{Socket.UdpCommunicationProviderOptions}" />
    /// <seealso cref="ICommunicationProvider" />
    [NameDescription("UdpCommunicationProvider", "Udp protocol for GodSharp.Communications")]
#pragma warning restore CS1658 // Warning is overriding an error
#pragma warning restore CS1584 // XML comment has syntactically incorrect cref attribute
    public sealed class UdpCommunicationProvider : CommunicationProviderBase<UdpCommunicationProviderOptions, IUdpCommunicationHandler>, IUdpCommunicationProvider
    {
        private Dictionary<string, UdpCommunicationDataEventHandler> OnData { get; set; }
        
        EventHandler<NetClientEventArgs<IUdpConnection>> OnException;

        Dictionary<int, UdpClient> clients = null;
        SocketConfig[] cConfigs = null;

        Dictionary<int, IUdpConnection> senders = null;

        bool initialized = false;
        bool started = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="UdpCommunicationProvider"/> class.
        /// </summary>
        public UdpCommunicationProvider()
        {
            this.OnData = new Dictionary<string, UdpCommunicationDataEventHandler>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UdpCommunicationProvider"/> class.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        public UdpCommunicationProvider(UdpCommunicationProviderOptions parameter)
        {
            Initialize(parameter);
        }

        /// <summary>
        /// Initializes the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">
        /// parameter
        /// or
        /// ServerClientConfigs
        /// </exception>
        /// <exception cref="InvalidOperationException">The provider is initialized</exception>
        /// <exception cref="ArgumentException">
        /// Configs
        /// or
        /// Configs
        /// </exception>
        private bool Initialize(UdpCommunicationProviderOptions parameter)
        {
            if (parameter == null) throw new ArgumentNullException(nameof(parameter));
            
            OnException = parameter.ExceptionHandler;

            if (parameter == null || parameter.Configs.Count < 1) throw new ArgumentNullException(nameof(parameter));

            if (initialized) throw new InvalidOperationException("The provider is initialized");

            if (parameter.Configs.GroupBy(x => x.Id).Any(x => x.ToArray().Length > 1)) throw new ArgumentException($"Duplicate 'Id' at {nameof(parameter.Configs)}");

            SocketConfig[] configs = parameter.Configs.Where(x => x.Available).ToArray();

            cConfigs = configs.Where(x => x.ConfigType == SocketConfigFlags.UdpClient).ToArray();

            if (cConfigs?.Length > 0)
            {
                clients = new Dictionary<int, UdpClient>();

                foreach (var item in cConfigs)
                {
                    if (clients.ContainsKey(item.Id)) continue;

                    clients.Add(item.Id, new UdpClient(item.Host, item.LocalPort.Value) { OnReceived = SocketClientDataHandler, OnStarted = null, OnStopped = null, OnException= SocketExceptionHandler });
                }
            }

            StateFlag = CommunicationModuleStateFlags.Initialized;

            initialized = true;

            return initialized;
        }

        /// <summary>
        /// Starts the specified objs.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Start(params int[] ids)
        {
            CheckingState(true, false, true);
                        
            List<int> cKeys = new List<int>();

            if (ids?.Length > 0)
            {
                if (clients?.Count > 0) cKeys = clients.Keys.Where(x => ids.Contains(x)).ToList();
            }
            else
            {
                cKeys = clients?.Keys.ToList();
            }
            
            if (cKeys?.Count > 0)
            {
                senders = new Dictionary<int, IUdpConnection>();

                foreach (var key in cKeys)
                {
                    try
                    {
                        clients[key].Start();
                        senders.Add(key, clients[key].Connection);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            return started;
        }

        /// <summary>
        /// Stops the specified objs.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Stop(params int[] ids)
        {
            CheckingState(true, true);

            if (clients?.Count > 0)
            {
                List<string> keys = new List<string>();

                foreach (var item in clients)
                {
                    try
                    {
                        item.Value.Stop();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Res the start.
        /// </summary>
        /// <param name="ids">The id array.</param>
        /// <returns></returns>
        public bool Restart(params int[] ids)
        {
            CheckingState(true, true);

            Stop();

            Start();

            return true;
        }

        /// <summary>
        /// Healthes the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        /// <exception cref="System.NotSupportedException"></exception>
        public IDictionary<int, CommunicationModuleStateFlags> Health(params int[] ids)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// Sockets the client data handler.
        /// </summary>
        /// <param name="args">The <see cref="NetClientEventArgs{IUdpConnection}"/> instance containing the event data.</param>
        private void SocketClientDataHandler(NetClientReceivedEventArgs<IUdpConnection> args)
        {
            try
            {
                Console.WriteLine($"udp [{args.LocalEndPoint}] received from [{args.RemoteEndPoint}]");

                IPEndPoint point = args.RemoteEndPoint;

                SocketConfig config = cConfigs.FirstOrDefault(x => x.Host == point.Address.ToString() && x.RemotePort == point.Port);

                SocketDataHandler(config, args.NetConnection, args.Buffers);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        /// <summary>
        /// Sockets the data handler.
        /// </summary>
        /// <param name="config">The configuration.</param>
        /// <param name="client">The client.</param>
        /// <param name="buffer">The buffer.</param>
        private void SocketDataHandler(SocketConfig config, IUdpConnection client, byte[] buffer)
        {
            if (config == null) return;

            OnData[config.EntryPoint]?.Invoke(config.EntryPoint, buffer, client);
        }

        /// <summary>
        /// Sockets the exception handler.
        /// </summary>
        /// <param name="args">The <see cref="NetClientEventArgs{IUdpConnection}"/> instance containing the event data.</param>
        private void SocketExceptionHandler(NetClientEventArgs<IUdpConnection> args)
        {
            Console.WriteLine($"udp [{args.LocalEndPoint}] throw exception:{args.Exception?.Message}");

            OnException?.Invoke(args);
        }

        /// <summary>
        /// Checkings the state.
        /// </summary>
        /// <param name="initialized">if set to <c>true</c> [initialized].</param>
        /// <param name="started">if set to <c>true</c> [started].</param>
        /// <param name="restarting">if set to <c>true</c> [restarting].</param>
        /// <exception cref="System.InvalidOperationException">
        /// The module is not initialized
        /// or
        /// The module is not started
        /// or
        /// The module is running
        /// </exception>
        private void CheckingState(bool initialized = false, bool started = false, bool restarting = false)
        {
            if (initialized && !this.initialized) throw new InvalidOperationException("The provider is not initialized");

            if (started && !this.started) throw new InvalidOperationException("The provider is not started");

            if (restarting && this.started) throw new InvalidOperationException("The provider is running");
        }

        /// <summary>
        /// Registers the modules.
        /// </summary>
        /// <param name="types">The types.</param>
        public void RegisterModules(Type[] types)
        {
            OnRegisterModules(types, (entry, handler) =>
            {
                if (!this.OnData.ContainsKey(entry)) this.OnData.Add(entry, handler.OnUdpDataHandler);
                else this.OnData[entry] += handler.OnUdpDataHandler;
            });
        }

        /// <summary>
        /// Gets the UDP connection.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        public IUdpConnection GetUdpConnection(int id)
        {
            if (senders.ContainsKey(id)) return senders[id];

            return null;
        }
    }
}